﻿using System;
using System.Collections.Generic;
using SlimDX;

namespace InsTron
{
    class GameState
    {
        public PlayerState[] PlayerState { get; private set; }
        public Stack<WallSegment>[] PlayerWalls { get; private set; }
        public bool[] Crashed { get; private set; }

        public GameState()
        {
            PlayerState = new PlayerState[2];
            PlayerWalls = new Stack<WallSegment>[2];
            Crashed = new bool[2];
            InitializePlayerState(0);
            InitializePlayerState(1);
        }


        private void InitializePlayerState(int idx)
        {
            float m = idx == 0 ? 1 : -1;
            PlayerState[idx] = new PlayerState(idx);
            PlayerWalls[idx] = new Stack<WallSegment>();
            PlayerWalls[idx].Push(new WallSegment(idx, PlayerState[idx].Position, PlayerState[idx].Direction));
            Crashed[idx] = false;
        }


        public bool Advance(long time)
        {
            Crashed[0] = AdvancePlayer(time, 0);
            Crashed[1] = AdvancePlayer(time, 1);

            return (Crashed[0] || Crashed[1]);
        }


        public bool AdvancePlayer(long time, int idx)
        {
            Vector3 oldFrontLeftPosition = PlayerState[idx].GetFrontLeftCoordinates();
            Vector3 oldFrontRightPosition = PlayerState[idx].GetFrontRightCoordinates();

            float distance = CalculateTravelDistance(time, idx);
            float moved = PlayerState[idx].MoveForward(distance);
            PlayerWalls[idx].Peek().Extend(moved);
            if (moved < distance)
            {
                TurnAndMove(idx, distance - moved);
            }

            Vector3 newFrontLeftPosition = PlayerState[idx].GetFrontLeftCoordinates();
            Vector3 newFrontRightPosition = PlayerState[idx].GetFrontRightCoordinates();

            return CheckForAnyCollisions(idx, oldFrontLeftPosition, oldFrontRightPosition, newFrontLeftPosition, newFrontRightPosition);
        }


        private void TurnAndMove(int idx, float remainderDistance)
        {
            PlayerState[idx].ExecuteTurn();
            PlayerWalls[idx].Push(new WallSegment(idx, PlayerState[idx].Position, PlayerState[idx].Direction));
            PlayerState[idx].MoveForward(remainderDistance);
            PlayerWalls[idx].Peek().Extend(remainderDistance);
        }


        private bool CheckForAnyCollisions(int idx, Vector3 oldFrontLeftPosition, Vector3 oldFrontRightPosition, Vector3 newFrontLeftPosition, Vector3 newFrontRightPosition)
        {
            return CheckForWallOrBikeCollissions(idx, ref oldFrontLeftPosition, ref newFrontLeftPosition) ||
                CheckForWallOrBikeCollissions(idx, ref oldFrontRightPosition, ref newFrontRightPosition) ||
                CheckForWallOrBikeCollissions(idx, ref newFrontLeftPosition, ref newFrontRightPosition) ||
                CheckForBorderCollisions(ref newFrontLeftPosition, ref newFrontRightPosition);
        }


        private float CalculateTravelDistance(long time, int idx)
        {
            float distance = Settings.BikeParameters[idx].Speed * time / 1000.0f;
            distance *= PlayerState[idx].GetSpeedMultiplier();
            return distance;
        }


        private bool CheckForBorderCollisions(ref Vector3 pointA, ref Vector3 pointB)
        {
            if (Math.Abs(pointA.X) >= Constants.MaxDistance ||
                Math.Abs(pointA.Z) >= Constants.MaxDistance ||
                Math.Abs(pointB.X) >= Constants.MaxDistance ||
                Math.Abs(pointB.Z) >= Constants.MaxDistance)
                return true;
            return false;
        }


        private bool CheckForWallOrBikeCollissions(int idx, ref Vector3 pointA, ref Vector3 pointB)
        {
            return CheckForWallCollisions(ref pointA, ref pointB, 0) ||
                CheckForWallCollisions(ref pointA, ref pointB, 1) || 
                CheckForBikeCollisions(idx == 0 ? 1 : 0, ref pointA, ref pointB);
        }


        private bool CheckForBikeCollisions(int anotherIdx, ref Vector3 pointA, ref Vector3 pointB)
        {
            WallSegment anotherPlayerBody = new WallSegment(anotherIdx, PlayerState[anotherIdx].Position, PlayerState[anotherIdx].Direction);
            anotherPlayerBody.Extend(Constants.TurningDistance);
            if (CheckForCollission(ref pointA, ref pointB, anotherPlayerBody))
                return true;

            return false;
        }


        private bool CheckForWallCollisions(ref Vector3 pointA, ref Vector3 pointB, int wallsIdx)
        {
            foreach (WallSegment ws in PlayerWalls[wallsIdx])
            {
                if (CheckForCollission(ref pointA, ref pointB, ws))
                    return true;
            }
            return false;
        }


        private bool CheckForCollission(ref Vector3 pointA, ref Vector3 pointB, WallSegment ws)
        {
            if (HaveTheSameDirection(ref pointA, ref pointB, ws))
            {
                return false;
            }

            return CheckForCllisionWithHorizontal(ref pointA, ref pointB, ws) ||
                CheckForCollisionWithVertical(ref pointA, ref pointB, ws);
        }


        private bool HaveTheSameDirection(ref Vector3 pointA, ref Vector3 pointB, WallSegment ws)
        {
            bool isSegmentVertical = pointA.X == pointB.X;
            bool isSegmentHorizontal = pointA.Z == pointB.Z;

            return ws.IsHorizontal && isSegmentHorizontal ||
                ws.IsVertical && isSegmentVertical;
        }


        private static bool CheckForCollisionWithVertical(ref Vector3 pointA, ref Vector3 pointB, WallSegment ws)
        {
            float wallX = ws.Begin.X;
            if (wallX >= Math.Min(pointA.X, pointB.X) && wallX <= Math.Max(pointA.X, pointB.X)) // 
            {
                float segmentMiddleZ = (pointA.Z + pointB.Z) * 0.5f;
                if (segmentMiddleZ >= Math.Min(ws.Begin.Z, ws.End.Z) && segmentMiddleZ <= Math.Max(ws.Begin.Z, ws.End.Z))
                    return true;
            }
            return false;
        }


        private static bool CheckForCllisionWithHorizontal(ref Vector3 pointA, ref Vector3 pointB, WallSegment ws)
        {
            float wallZ = ws.Begin.Z;
            if (wallZ >= Math.Min(pointA.Z, pointB.Z) && wallZ <= Math.Max(pointA.Z, pointB.Z)) // 
            {
                float segmentMiddleX = (pointA.X + pointB.X) * 0.5f;
                if (segmentMiddleX >= Math.Min(ws.Begin.X, ws.End.X) && segmentMiddleX <= Math.Max(ws.Begin.X, ws.End.X))
                    return true;
            }
            return false;
        }


        public void InitiateTurnPlayer(int idx, bool left)
        {
            if (!PlayerState[idx].IsTurning)
                PlayerState[idx].InitiateTurn(left);
        }
    }
}
